﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using NextGEN.Util.Portable.Common.Library.Annotations;
using NextGEN.Util.Portable.Common.Library.Entities.Attributes;
using NextGEN.Util.Portable.Common.Library.Entities.Interfaces;
using NextGEN.Util.Portable.Common.Library.Exceptions;
using NextGEN.Util.Portable.Common.Library.Results;

namespace NextGEN.Util.Portable.Common.Library.Entities
{
    [DataContract]
    public abstract class BaseEntity : 
        INotifyPropertyChanged, 
        IEqualityComparer<BaseEntity>, 
        IDisposable
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged();
                }
            }
        }
        
        protected BaseEntity()
        {

        }
        
        #region Dispose Pattern

        private bool _disposed;
        ~BaseEntity()
        {
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    // dispose-only, i.e. non-finalizable logic
                }

                // shared cleanup logic
                _disposed = true;
            }
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        public virtual Dictionary<string, ValidationResult> Validate()
        {
            var propertyInfos = GetType().GetRuntimeProperties();
            return propertyInfos.ToDictionary(propertyInfo => propertyInfo.Name, propertyInfo => Validate(propertyInfo.Name));
        }

        protected virtual ValidationResult Validate(string propertyName)
        {
            var propertyInfo = GetType().GetRuntimeProperty(propertyName);
            var validationAttributes =
                propertyInfo.GetCustomAttributes(typeof (BasePropertyAttribute)).OfType<IValidate>();

            if (validationAttributes != null)
            {
                var propertyValue = propertyInfo.GetValue(this);
                var validationResults =
                    validationAttributes.Select(validationAttribute => validationAttribute.IsValid(propertyValue))
                        .ToList();
                return new ValidationResult(validationResults);
            }
            
            return null;
        }

        private void ValidateAuto(string propertyName)
        {
            var propertyInfo = GetType().GetRuntimeProperty(propertyName);
            var validationAttributes =
                propertyInfo.GetCustomAttributes(typeof (BasePropertyAttribute))
                    .OfType<BasePropertyAttribute>()
                    .Where(attribute => attribute.IsWorkedAuto)
                    .OfType<IValidate>(); 
            

            if (validationAttributes != null)
            {
                var propertyValue = propertyInfo.GetValue(this);
                var validationResults =
                    validationAttributes.Select(validationAttribute => validationAttribute.IsValid(propertyValue))
                        .ToList();

                var validationResult = new ValidationResult(validationResults);
                throw new AggregateException("Doğrulama sırasında hata oluştu", validationResult.ErrorMessages);
            }
        }

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            ValidateAuto(propertyName);

            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region IEqualityComparer

        public virtual bool Equals(BaseEntity x, BaseEntity y)
        {
            return ReferenceEquals(x, y);
        }

        public int GetHashCode(BaseEntity obj)
        {
            return RuntimeHelpers.GetHashCode(obj);
        }

        #endregion
    }
}
